/* Copyright (C) 2015 Boren Zhang <bobo1993324@gmail.com>

   This file is part of Dekko email client for Ubuntu Devices/

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License or (at your option) version 3

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "ContactsModel.h"
#include <QDebug>
#include <QDir>
#include <QSqlQuery>
#include <QSqlResult>
#include <QSqlRecord>
#include <QSqlError>
#include <QDir>
#include <cstdlib>
#include <QCryptographicHash>
#include <QDateTime>
#include <QTimer>
#include "configure.cmake.h"

namespace Dekko {
namespace Contacts  {

ContactsModel::ContactsModel(QObject *parent)
    : QAbstractListModel(parent), m_databasePath("") {

}

ContactsModel::ContactsModel(QObject *parent, QString databasePath)
    : QAbstractListModel(parent), m_databasePath(databasePath) {

}

ContactsModel::~ContactsModel()
{
    m_db.close();
    emit beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
    for (int i = 0; i < m_contactList.count(); i++) {
        delete m_contactList[i];
    }
    m_contactList.clear();
    emit endRemoveRows();
}

void ContactsModel::removeContact(QString contactId)
{
    int idx = indexOfContactId(contactId);

    QSqlQuery query(QString("DELETE FROM contact WHERE id = ?"), m_db);
    query.bindValue(0, contactId);
    query.exec();

    QSqlQuery removeEmailsQuery(QString("DELETE FROM email WHERE id = ?"), m_db);
    removeEmailsQuery.bindValue(0, contactId);
    removeEmailsQuery.exec();

    emit beginRemoveRows(QModelIndex(), idx, idx);
    m_contactList.removeAt(idx);
    m_contactMap.remove(contactId);
    emit endRemoveRows();
}

QString ContactsModel::newContact(QString firstName, QString lastName, QString organization, QVariantList emails)
{
    QCryptographicHash hash(QCryptographicHash::Sha1);
    QString hashInput = (firstName + " " + lastName + (QString("%1").arg(rand())));
    hash.addData(hashInput.toLocal8Bit());
    QString contactId = QString(hash.result().toHex());

    Contact *c = new Contact(contactId, firstName, lastName, organization);
    for (int i = 0; i < emails.length(); i++) {
        c->emails.append(emails[i].toString());
    }

    QSqlQuery query(m_db);
    query.prepare("INSERT INTO contact(id, firstName, lastName, organization) VALUES(?, ?, ?, ?)");
    query.bindValue(0, c->id);
    query.bindValue(1, c->firstName);
    query.bindValue(2, c->lastName);
    query.bindValue(3, c->organization);
    query.exec();

    for (int i = 0; i < emails.length(); i++) {
        query.prepare("INSERT INTO email VALUES (?, ?)");
        query.bindValue(0, c->id);
        query.bindValue(1, c->emails[i]);
        query.exec();
    }

    emit beginInsertRows(QModelIndex(), m_contactList.length(), m_contactList.length());
    m_contactList.push_back(c);
    m_contactMap[c->id] = c;
    emit endInsertRows();

    return contactId;
}

void ContactsModel::updateContact(QVariantMap contactMap)
{
    QString contactId = contactMap.value("id").toString();
    int idx = indexOfContactId(contactId);
    Contact *contact = m_contactList[idx];
    QSqlQuery query(m_db);
    if (contactMap.contains("firstName")) {
        QString firstName = contactMap.value("firstName").toString();
        if (firstName != contact->firstName) {
            query.prepare("UPDATE contact SET firstName = ? WHERE id = ?");
            query.bindValue(0, firstName);
            query.bindValue(1, contactId);
            query.exec();
            contact->firstName = firstName;
        }
    }
    if (contactMap.contains("lastName")) {
        QString lastName = contactMap.value("lastName").toString();
        if (lastName != contact->lastName) {
            query.prepare("UPDATE contact SET lastName = ? WHERE id = ?");
            query.bindValue(0, lastName);
            query.bindValue(1, contactId);
            query.exec();
            contact->lastName = lastName;
        }
    }
    if (contactMap.contains("organization")) {
        QString organization = contactMap.value("organization").toString();
        if (organization != contact->organization) {
            query.prepare("UPDATE contact SET organization = ? WHERE id = ?");
            query.bindValue(0, organization);
            query.bindValue(1, contactId);
            query.exec();
            contact->organization = organization;
        }
    }
    if (contactMap.contains("emails")) {
        query.prepare("DELETE FROM email where id = ?");
        query.bindValue(0, contactId);
        query.exec();
        QList<QVariant> emails = contactMap.value("emails").toList();
        contact->emails.clear();
        for (int i = 0; i < emails.length(); i++) {
            contact->emails.append(emails[i].toString());
        }
        for (int i = 0; i < contact->emails.length(); i++) {
            query.prepare("INSERT INTO email VALUES (?, ?)");
            query.bindValue(0, contact->id);
            query.bindValue(1, contact->emails[i]);
            query.exec();
        }
    }
    emit dataChanged(index(idx), index(idx));
}

void ContactsModel::setStarred(QString contactId, bool starred)
{
    int contactIndex = indexOfContactId(contactId);
    if (contactIndex == -1) {
        return;
    }
    if (m_contactList[contactIndex]->starred != starred) {
        m_contactList[contactIndex]->starred = starred;
        QSqlQuery query(m_db);
        query.prepare("UPDATE contact SET starred = ? where id = ?");
        query.bindValue(0, starred ? 1 : 0);
        query.bindValue(1, contactId);
        query.exec();
        emit dataChanged(index(contactIndex), index(contactIndex), QVector<int>() << RoleStarred);
    }
}

void ContactsModel::setLastContactTime(QString contactId, QDateTime lastContactTime)
{
    int contactIndex = indexOfContactId(contactId);
    if (contactIndex == -1) {
        return;
    }
    m_contactList[contactIndex]->lastContactTime = lastContactTime;
    QSqlQuery query(m_db);
    query.prepare("UPDATE contact SET lastContactTime = ? where id = ?");
    query.bindValue(0, lastContactTime.toString("yyyyMMddHHmm"));
    query.bindValue(1, contactId);
    query.exec();
    emit dataChanged(index(contactIndex), index(contactIndex), QVector<int>() << RoleLastContactDate);
}

int ContactsModel::rowCount(const QModelIndex &parent) const
{
    return m_contactList.count();
}

QVariant ContactsModel::data(const QModelIndex &index, int role) const
{
    if (index.row() < 0 || index.row() > m_contactList.count() - 1)
        return QVariant();
    Contact *c = m_contactList[index.row()];
    switch (role) {
    case RoleFirstName:
        return c->firstName;
    case RoleLastName:
        return c->lastName;
    case RoleEmails:
        return QVariant::fromValue(c->emails);
    case RoleTag:
        if (!c->firstName.isEmpty()) {
            return c->firstName + " " + c->lastName;
        }
        if (!c->lastName.isEmpty()) {
            return c->lastName;
        }
        return "#";
    case RoleContactId:
        return c->id;
    case RoleOrganization:
        return c->organization;
    case RoleStarred:
        return c->starred;
    case RoleLastContactDate:
        return c->lastContactTime;
    case RoleLastContactDateTag:
        if (c->lastContactTime.date() == QDateTime::currentDateTime().date()) {
            return tr("Today");
        } else if (c->lastContactTime.date().addDays(-1) == QDateTime::currentDateTime().date()) {
            return tr("Yesterday");
        } else {
            return c->lastContactTime.toString("MMM d");
        }
    }
    return QVariant();
}

QHash<int, QByteArray> ContactsModel::roleNames() const
{
    static QHash<int, QByteArray> roleNames;
    if (roleNames.isEmpty()) {
        roleNames[RoleFirstName] = "firstName";
        roleNames[RoleLastName] = "lastName";
        roleNames[RoleEmails] = "emails";
        roleNames[RoleTag] = "tag";
        roleNames[RoleContactId] = "contactId";
        roleNames[RoleOrganization] = "organization";
        roleNames[RoleStarred] = "starred";
        roleNames[RoleLastContactDate] = "lastContactDate";
        roleNames[RoleLastContactDateTag] = "lastContactDateTag";
    }
    return roleNames;
}

void ContactsModel::initDB()
{
    connectDB();
    createOrUpdateDatabaseIfNecessary();
    readData();
}

void ContactsModel::connectDB()
{

    m_db = QSqlDatabase::addDatabase("QSQLITE", m_databaseName);
    if (m_databasePath.length() > 0) {
        m_db.setDatabaseName(m_databasePath);
    } else {
        QString path;
#ifdef CLICK_MODE
        path = QStringLiteral("%1/.local/share/dekko.dekkoproject/")
                .arg(QDir::homePath());
#else
        path = QStringLiteral("%1/.local/share/dekkoproject/dekko/")
                .arg(QDir::homePath());
#endif
        QDir dir(path);
        if (!dir.exists()) {
            dir.mkpath(".");
        }
        m_db.setDatabaseName(path.append(QStringLiteral("%1.sqlite").arg(m_databaseName)));
    }
    qDebug() << "DatabaseName: " << m_db.databaseName();
    if (!m_db.open()) {
        qDebug() << "Failed to open db. Error: " << m_db.lastError();
    }
}

void ContactsModel::createOrUpdateDatabaseIfNecessary()
{
    QSqlQuery query("SELECT * FROM version", m_db);
    int version = 0;
    if (!query.exec()) {
        query.exec("CREATE TABLE version ( version INTEGER )");
        query.exec("INSERT INTO version VALUES ( 1 )");
        query.exec("CREATE TABLE contact (id TEXT PRIMARY KEY, firstName TEXT, lastName TEXT, organization TEXT, starred INTEGER)");
        query.exec("CREATE TABLE email (id TEXT, email TEXT)");
        version = 1;
    } else {
        query.next();
        version = query.record().value(0).toInt();
    }
    if (version == 1) {
        query.exec("ALTER TABLE contact ADD COLUMN lastContactTime TEXT");
        query.exec("UPDATE version SET version = 2");
    }
}

void ContactsModel::readData()
{
    QSqlQuery query("SELECT * from contact", m_db);
    while (query.next()) {
        QString contactId = query.record().value(0).toString();
        QString firstName = query.record().value(1).toString();
        QString lastName = query.record().value(2).toString();
        QString organization = query.record().value(3).toString();
        bool starred = query.record().value(4).toInt() != 0;
        QDateTime lastContactTime = QDateTime::fromString(query.record().value(5).toString(), "yyyyMMddHHmm");

        Contact *c = new Contact(contactId, firstName, lastName, organization);
        c->starred = starred;
        c->lastContactTime = lastContactTime;

        emit beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_contactList.append(c);
        m_contactMap[c->id] = c;
        emit endInsertRows();
    }
    query.exec("SELECT * from email");
    while (query.next()) {
        QString id = query.record().value(0).toString();
        QString email = query.record().value(1).toString();
        if (m_contactMap.contains(id))
            m_contactMap[id]->emails.append(email);
    }
    emit dataChanged(index(0), index(rowCount() - 1), QVector<int>() << RoleEmails);
}

int ContactsModel::indexOfContactId(QString contactId)
{
    for (int i = 0; i < m_contactList.size(); i++) {
        if (contactId == m_contactList[i]->id)
            return i;
    }
    return -1;
}

QVariantMap ContactsModel::getByIndex(int idx) {
    QVariantMap map;
    Contact *c = m_contactList[idx];
    map["contactId"] = c->id;
    map["firstName"] = c->firstName;
    map["lastName"] = c->lastName;
    map["organization"] = c->organization;
    QVariantList list;
    for (int i = 0; i < c->emails.length(); i++) {
        list.append(QVariant::fromValue(c->emails[i]));
    }
    map["emails"] = list;
    return map;
}

QString ContactsModel::getContactIdWithEmail(QString email)
{
    for (int i = 0; i < m_contactList.length(); i++) {
        Contact * contact = m_contactList[i];
        for (int j = 0; j < contact->emails.length(); j++) {
            if (contact->emails[j] == email)
                return contact->id;
        }
    }
    return "";
}

void ContactsModel::removeStaleContactMoreThan(int maxCount)
{
    if (m_contactList.size() > maxCount) {
        QList<QString> contactIdsToRemove;

        QSqlQuery query("SELECT id FROM contact ORDER BY lastContactTime ASC", m_db);
        query.exec();

        while(query.next() &&
              m_contactList.count() - contactIdsToRemove.count() > maxCount) {
            contactIdsToRemove.append(query.record().value(0).toString());
        }

        for (int i = 0; i < contactIdsToRemove.length(); i++) {
            removeContact(contactIdsToRemove[i]);
        }
    }
}

QString ContactsModel::databaseName()
{
    return m_databaseName;
}

void ContactsModel::setDatabaseName(QString databaseName)
{
    m_databaseName = databaseName;
    // We don't need this straight away so delay it loading
    initDB();
}

}
}
